<?php
namespace app\home\controller;

use app\home\model\ThirdApp;
use app\home\model\AuthCode;
use passport\LoginHelper;
use nekoing\BaseController;
use app\home\model\Openid;
use app\home\validate\OauthJumpValidate;
use nekoing\AppException;
use nekoing\ErrorCode;
use app\home\validate\OauthTokenValidate;
use app\home\model\AccessToken;

class Oauth2 extends BaseController
{

    const AUTHORIZE_TOKEN_LIFE = 7200;

    public function authorize()
    {
        $responseType = input('get.response_type', 'code');
        $appId = input('get.client_id');
        $redirectUri = input('get.redirect_uri');
        $state = input('get.state', '');
        $scope = input('get.scope', config('oauth.default_scope'));
        
        $app = ThirdApp::get($appId);
        
        if (! $app) {
            throw new AppException(100000, 'client_id错误');
        }
        
        if ($app->status) {
            throw new AppException(100001, 'client_id暂停使用');
        }
        
        $this->checkDirectUri($app, $redirectUri);
        
        $this->redirect(url('show') . '?' . http_build_query([
            'response_type' => $responseType,
            'client_id' => $appId,
            'redirect_uri' => $redirectUri,
            'state' => $state,
            'scope' => $scope
        ]));
    }

    public function show()
    {
        $responseType = input('get.response_type', 'code');
        $appId = input('get.client_id');
        $redirectUri = input('get.redirect_uri');
        $state = input('get.state', '');
        $scope = input('get.scope', config('oauth.default_scope'));
        
        $app = ThirdApp::get($appId);
        if (! $app) {
            throw new AppException(100000, 'client_id错误');
        }
        
        if ($app->status) {
            throw new AppException(100001, 'client_id暂停使用');
        }
        
        $scopeArr = $this->checkScope($scope, $redirectUri, $state);
        
        return $this->fetch('', [
            'response_type' => $responseType,
            'client_id' => $appId,
            'redirect_uri' => $redirectUri,
            'state' => $state,
            'scope' => $scope,
            'user' => LoginHelper::getUser(),
            'scopeArr' => $scopeArr,
            'scopeInfo' => AccessToken::scopeInfo()
        ]);
    }

    public function login()
    {
        $username = input('post.username');
        $password = input('post.password');
        
        LoginHelper::login($username, $password);
        $user = LoginHelper::getUser()->toArray();
        
        $this->ajaxSuccess([
            'user' => $user
        ]);
    }

    public function jump()
    {
        $responseType = input('post.response_type', 'code');
        $appId = input('post.client_id');
        $redirectUri = input('post.redirect_uri');
        $state = input('post.state', '');
        $scope = input('post.scope', AccessToken::SCOPE_BASIC);
        
        $validate = new OauthJumpValidate();
        if (! $validate->check($_POST)) {
            throw new AppException(ErrorCode::INVALID_PARAM, $validate->getError());
        }
        
        if (LoginHelper::isLogin()) {
            $user = LoginHelper::getUser();
            $app = ThirdApp::get($appId);
            $openid = Openid::getOpenid($user->id, $appId);
            
            if (! $app) {
                throw new AppException(100000, 'client_id错误');
            }
            
            if ($app->status) {
                throw new AppException(100001, 'client_id暂停使用');
            }
            
            $this->checkDirectUri($app, $redirectUri);
            
            $scopeArr = $this->checkScope($scope, $redirectUri, $state);
            
            AuthCode::destroy([
                'client_id' => $appId,
                'openid' => $openid
            ]);
            
            $authCode = new AuthCode();
            
            $authCode->scope = $scope;
            $authCode->client_id = $appId;
            $authCode->openid = $openid;
            $authCode->redirect_uri = $redirectUri;
            
            $authCode->save();
            
            switch ($responseType) {
                case 'code':
                    $this->codeReturn($redirectUri, $state, $authCode->code);
                default:
                    throw new AppException(100004, "response_type错误");
            }
        }
    }

    public function token()
    {
        $grantType = input('get.grant_type');
        $appId = input('get.client_id');
        $appSecret = input('get.app_secret');
        $code = input('get.code');
        $redirectUri = input('get.redirect_uri');
        
        $validate = new OauthTokenValidate();
        if (! $validate->check($_GET)) {
            $this->ajaxFail(ErrorCode::INVALID_PARAM, $validate->getError());
        }
        
        if($grantType != 'authorization_code') {
            $this->ajaxFail(100007, 'grant_type错误');
        }
        
        $app = ThirdApp::get($appId);
        if (! $app) {
            $this->ajaxFail(100000, 'client_id错误');
        }
        
        if ($app->status) {
            $this->ajaxFail(100001, 'client_id暂停使用');
        }
        
        if ($app->secret !== $appSecret) {
            $this->ajaxFail(100004, 'secret不正确');
        }
        
        $authCode = AuthCode::get([
            'client_id' => $appId,
            'code' => $code
        ]);

        if (! $authCode) {
            $this->ajaxFail(100005, 'code不正确');
        }
        
        if ($authCode->expire + $authCode->create_time < CURRENT_TIMESTAMP) {
            $this->ajaxFail(100006, 'code已过期');
        }
        
        if($redirectUri != $authCode->redirect_uri) {
            $this->ajaxFail(100008, 'redirect_uri不匹配');
        }
        
        $token = AccessToken::build($authCode);
        
        $this->ajaxSuccess($token->getShowData());
    }
    
    public function refresh()
    {
        $grantType = input('grant_type');
        $appId = input('get.client_id');
        $refreshToken = input('get.refresh_token');
        
        if($grantType != 'refresh_token') {
            $this->ajaxFail(100008, 'grant_type错误');
        }
        
        $accessToken = AccessToken::get([
            'refresh_token' => $refreshToken
        ]);
        
        if(! $accessToken) {
            $this->ajaxFail(100009, 'refresh_token错误');
        }
        
        if($accessToken->refresh_expire + $accessToken->create_time < CURRENT_TIMESTAMP) {
            $this->ajaxFail(100010, 'refresh_token已过期');
        }
        
        if($accessToken->refreshed) {
            $this->ajaxFail(100011, 'refresh_token已失效');
        }
        
        $new = $accessToken->refersh();
        $this->ajaxSuccess($new->getShowData());
    }

    public function check()
    {
        $openid = input('get.openid');
        $token = input('get.access_token');
        
        AccessToken::check($token, $openid, null);
        
        $this->ajaxSuccess();
    }
    
    protected function checkDirectUri(ThirdApp $app, $redirectUri)
    {
        $pattern = '%^https?://' . $app->domain . '(/|$)%i';
        if (! preg_match($pattern, $redirectUri)) {
            throw new AppException(100002, "参数redirect_uri非法或与注册域名不是同一个网站");
        }
    }

    protected function checkScope($scope, $redirectUri, $state)
    {
        $scope = array_unique(explode(',', $scope));
        $availableScopes = AccessToken::availableScopes();
        foreach ($scope as $item) {
            if (! in_array($item, $availableScopes)) {
                throw new AppException(100003, 'scope参数错误');
            }
        }
        return $scope;
    }

    protected function codeReturn($redirectUri, $state, $code, $message = null)
    {
        $params = [
            'code' => $code,
            'state' => $state
        ];
        
        if ($message) {
            $params['msg'] = $message;
        }
        
        if ($redirectUri) {
            $redirectUri .= (strpos('?', $redirectUri) === false ? '?' : '&') . http_build_query($params);
            $this->redirect($redirectUri);
            exit();
        } else {
            throw new AppException($code, $message);
            exit();
        }
    }
}

?>